<?php
define('G_REDIS_HOST', 'my56class.redis.rds.aliyuncs.com'); //redisIP
define('G_REDIS_PORT', '6379'); //redis端口
define('G_REDIS_PASS','Aa1213141516'); //redis密码

define("G_SESSION_EXPIRE", "7200"); //session过期时间，单位秒
define("G_SESSION_DOMAIN", ".local.amanwu.com"); //session域名（多子域名共享SESSION，配置为根域名，默认当前域名）

global $_my_session_flags;
$_my_session_flags = array(
	'started' => false,
	'last_time' => 0,
	'load_hash' => '',
);

function my_session_start() {
	global $_my_session_flags;
	if($_my_session_flags['started']) {
		return; //已开启session
	}
	$_my_session_flags['started'] = true;
	
	//设置session id与cookie
	if(isset($_COOKIE['PHPSESSID']) && strlen($_COOKIE['PHPSESSID']) >= 8) {
		$sid = $_COOKIE['PHPSESSID'];
	} else {
		$sid = md5($_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].microtime().mt_rand());
		$_COOKIE['PHPSESSID'] = $sid;
		if(defined("G_SESSION_DOMAIN") && G_SESSION_DOMAIN != '') {
			$domain = G_SESSION_DOMAIN;
		} else {
			$domain = $_SERVER['HTTP_HOST'];
			$domainArr = explode(':', $domain, 2);
			if(count($domainArr) == 2 && ($domainArr[1] == 80 || $domainArr[1] == 443)) {
				$domain = $domainArr[0];
			}
		}
		setcookie("PHPSESSID", $sid, 0, "/", $domain);		
	}
	session_id($sid);
	//设置session全局变量
	$sessionKey = 'PHPSESS-' . $sid;
	$data = util_Redis::get($sessionKey);
	$_SESSION = array();
	if($data != false && time() - G_SESSION_EXPIRE < $data['last_time']) {
		$_my_session_flags['last_time'] = $data['last_time'];
		$_my_session_flags['load_hash'] = md5($data['session']);
		$_SESSION = json_decode($data['session'], true);
	}
	//PHP进程结束前操作
	register_shutdown_function("my_session_end");
}

function my_session_end() {
	global $_my_session_flags;
	$sessionStr = json_encode($_SESSION);
	$needSave = false;
	if($_my_session_flags['load_hash'] != md5($sessionStr)) {
		$needSave = true; //内容发生变化时保存
	} else if(!empty($_SESSION)) {
		$pastTime = time() - $_my_session_flags['last_time'];
		if($pastTime > 0 && G_SESSION_EXPIRE / $pastTime < 10) {
			$needSave = true; //时间过1/10后操作，自动续期
		}
	}
	if($needSave) {
		$sessionKey = 'PHPSESS-' . session_id();
		$data = array(
			'last_time' => time(),
			'session' => $sessionStr,
		);
		util_Redis::setex($sessionKey, $data, G_SESSION_EXPIRE);
	}
}


class _RedisConnection {
	public $hostname = 'localhost';
	/**
	 * @var integer the port to use for connecting to the redis server. Default port is 6379.
	 * If [[unixSocket]] is specified, hostname and port will be ignored.
	 */
	public $port = 6379;
	/**
	 * @var string the unix socket path (e.g. `/var/run/redis/redis.sock`) to use for connecting to the redis server.
	 * This can be used instead of [[hostname]] and [[port]] to connect to the server using a unix socket.
	 * If a unix socket path is specified, [[hostname]] and [[port]] will be ignored.
	 * @since 2.0.1
	 */
	public $unixSocket;
	/**
	 * @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send.
	 * See http://redis.io/commands/auth
	 */
	public $password;
	/**
	 * @var integer the redis database to use. This is an integer value starting from 0. Defaults to 0.
	 */
	public $database = 0;
	/**
	 * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
	 */
	public $connectionTimeout = null;
	/**
	 * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
	 */
	public $dataTimeout = null;
	/**
	 * @var resource redis socket connection
	 */
	private $_socket;

	public function __construct($host, $port) {
		$this->hostname = $host;
		$this->port = $port;
	}



	/**
	 * Closes the connection when this component is being serialized.
	 * @return array
	 */
	public function __sleep()
	{
		$this->close();

		return array_keys(get_object_vars($this));
	}

	/**
	 * Returns a value indicating whether the DB connection is established.
	 * @return boolean whether the DB connection is established
	 */
	public function getIsActive()
	{
		return $this->_socket !== null;
	}

	/**
	 * Establishes a DB connection.
	 * It does nothing if a DB connection has already been established.
	 * @throws Exception if connection fails
	 */
	public function open()
	{
		if ($this->_socket !== null) {
			return;
		}
		$this->_socket = @stream_socket_client(
			$this->unixSocket ? 'unix://' . $this->unixSocket : 'tcp://' . $this->hostname . ':' . $this->port,
			$errorNumber,
			$errorDescription,
			$this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout")
		);
		if ($this->_socket) {
			if ($this->dataTimeout !== null) {
				stream_set_timeout($this->_socket, $timeout = (int) $this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000));
			}
			if ($this->password !== null) {
				$this->executeCommand('AUTH', array($this->password));
			}
			$this->executeCommand('SELECT', array($this->database));
		} else {
			$message = "Failed to open redis DB connection ($connection): $errorNumber - $errorDescription";
			throw new Exception($message, $errorDescription, (int) $errorNumber);
		}
	}

	/**
	 * Closes the currently active DB connection.
	 * It does nothing if the connection is already closed.
	 */
	public function close()
	{
		if ($this->_socket !== null) {
			$this->executeCommand('QUIT');
			stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR);
			$this->_socket = null;
		}
	}

	/**
	 * Allows issuing all supported commands via magic methods.
	 *
	 * ```php
	 * $redis->hmset(['test_collection', 'key1', 'val1', 'key2', 'val2'])
	 * ```
	 *
	 * @param string $name name of the missing method to execute
	 * @param array $params method call arguments
	 * @return mixed
	 */
	public function __call($name, $params) {
		return $this->executeCommand($name, $params);
	}

	/**
	 * Executes a redis command.
	 * For a list of available commands and their parameters see http://redis.io/commands.
	 *
	 * @param string $name the name of the command
	 * @param array $params list of parameters for the command
	 * @return array|bool|null|string Dependent on the executed command this method
	 * will return different data types:
	 *
	 * - `true` for commands that return "status reply" with the message `'OK'` or `'PONG'`.
	 * - `string` for commands that return "status reply" that does not have the message `OK` (since version 2.0.1).
	 * - `string` for commands that return "integer reply"
	 *   as the value is in the range of a signed 64 bit integer.
	 * - `string` or `null` for commands that return "bulk reply".
	 * - `array` for commands that return "Multi-bulk replies".
	 *
	 * See [redis protocol description](http://redis.io/topics/protocol)
	 * for details on the mentioned reply types.
	 * @throws Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply).
	 */
	public function executeCommand($name, $params = array()) {
		$this->open();

		array_unshift($params, $name);
		$command = '*' . count($params) . "\r\n";
		foreach ($params as $arg) {
			$command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n";
		}

		fwrite($this->_socket, $command);

		return $this->parseResponse(implode(' ', $params));
	}

	/**
	 * @param string $command
	 * @return mixed
	 * @throws Exception on error
	 */
	private function parseResponse($command)
	{
		if (($line = fgets($this->_socket)) === false) {
			throw new Exception("Failed to read from socket.\nRedis command was: " . $command);
		}
		$type = $line[0];
		$line = mb_substr($line, 1, -2, '8bit');
		switch ($type) {
			case '+': // Status reply
				if ($line === 'OK' || $line === 'PONG') {
					return true;
				} else {
					return $line;
				}
			case '-': // Error reply
				throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command);
			case ':': // Integer reply
				// no cast to int as it is in the range of a signed 64 bit integer
				return $line;
			case '$': // Bulk replies
				if ($line == '-1') {
					return null;
				}
				$length = $line + 2;
				$data = '';
				while ($length > 0) {
					if (($block = fread($this->_socket, $length)) === false) {
						throw new Exception("Failed to read from socket.\nRedis command was: " . $command);
					}
					$data .= $block;
					$length -= mb_strlen($block, '8bit');
				}

				return mb_substr($data, 0, -2, '8bit');
			case '*': // Multi-bulk replies
				$count = (int) $line;
				$data = array();
				for ($i = 0; $i < $count; $i++) {
					$data[] = $this->parseResponse($command);
				}

				return $data;
			default:
				throw new Exception('Received illegal data from redis: ' . $line . "\nRedis command was: " . $command);
		}
	}

}
class util_Redis {
	private static $instance = null;
	private static $prefixQueueId = "QUEUE_ID_";
	private $memcache_Instance = null;
	
	private static function init() {
		if(self::$instance != null) {
			return;
		}
		self::$instance = new _RedisConnection(G_REDIS_HOST, G_REDIS_PORT);
		if(defined("G_REDIS_PASS") && G_REDIS_PASS != '') {
			self::$instance->password = G_REDIS_PASS;
		}
	}
	
	public static function set($key, $value, $timeout=0) {
		if($timeout > 0) {
			return self::setex($key, $value, $timeout);
		}
		self::init(); //自动连接Redis
		self::$instance->set($key, json_encode($value));
		return true;
	}
	
	public static function setex($key, $value, $timeout) {
		self::init(); //自动连接Redis
		self::$instance->setex($key, $timeout, json_encode($value));
		return true;
	}
	
	public static function get($key) {
		self::init(); //自动连接Redis
		$value = self::$instance->get($key);
		if($value == false) {
			return false;
		}
		return json_decode($value, true);
	}
	
	public static function remove($key) {
		self::init(); //自动连接Redis
		return self::$instance->del($key);
	}
	
	public static function getStats() {
		self::init(); //自动连接Redis
		return self::$instance->getOption();
	}

	public static function bpopEvent($queueId, $timeout) {
		self::init(); //自动连接Redis
		$queueId = self::$prefixQueueId . $queueId;
		$value = self::$instance->blPop($queueId, $timeout);
		if(empty($value)) {
			return false;
		}
		if(count($value) == 2) {
			$value = $value[1];
		} else {
			$value = $value[0];
		}
		return json_decode($value, true);
	}

	public static function rock($key, $timeout=8) {
		self::init(); //自动连接Redis
		return self::$instance->set($key, time(), "EX", $timeout, "NX");
	}





}